﻿// Spun AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Formality.Parsing.ContextFree;

namespace Formality.FormalSystem.Parser
{
    public class GrammarBuilder
    {
        public GrammarBuilder(Terminals Terminals, PrecedenceRules PrecedenceRules, ISentenceFactory Factory)
        {
            this.PrecedenceRules = PrecedenceRules;

            var T = Terminals;

            var Sentence = new Nonterminal<Sentence>("sentence");
            var AntecedentList = new Nonterminal<List<Proposition>>("antecedent-list");
            var Proposition = new Nonterminal<Proposition>("proposition");
            var OperatorList = new Nonterminal<InfixOperatorList>("operator-list");
            var UnaryOperator = new Nonterminal<Proposition>("unary-operator");
            var Atom = new Nonterminal<Proposition>("atom");

            var Variable = new Nonterminal<Variable>("variable");
            var VariableList = new Nonterminal<List<Variable>>("variable-list");

            Sentence.add(AntecedentList, T.INFERS, Proposition, (a, i, p) => Factory.Axiom(a, p));
            Sentence.add(Proposition, T.DEFINED, Proposition, (p1, i, p2) => Factory.Definition(p1, p2));
            Sentence.add(Proposition, p => p);

            AntecedentList.add(AntecedentList, T.COMMA, Proposition, (a, c, p) => { a.Add(p); return a; });
            AntecedentList.add(Proposition, p => new List<Proposition>(new[] { p }));
            AntecedentList.add(() => new List<Proposition>());

            Proposition.add(OperatorList, o => InfixOperator(Factory, o));
            Proposition.add(UnaryOperator, u => u);

            OperatorList.add(OperatorList, T.OPERATOR, UnaryOperator, (l, op, u) => l.Add(op, u));
            OperatorList.add(UnaryOperator, T.OPERATOR, UnaryOperator, (u1, op, u2) => InfixOperatorList.New(u1, op, u2));

            UnaryOperator.add(T.BINDER, Variable, UnaryOperator, (b, v, s) => Factory.BindingOperator(b, v, s));
            UnaryOperator.add(T.OPERATOR, UnaryOperator, (s, o) => Factory.PrefixOperator(s, o.Singleton()));
            UnaryOperator.add(T.THE, Variable, Variable, UnaryOperator, (t, f, v, s) => Factory.BindingOperator(t, v, Factory.Function(f, v), s));
            UnaryOperator.add(Atom, a => a);

            Atom.add(Variable, v => v);
            Atom.add(Variable, Variable, (f, a) => Factory.Function(f, a));
            Atom.add(T.IDENT, T.LEFT, VariableList, T.RIGHT, (f, l, a, r) => Factory.Function(Factory.Variable(f), a.ToArray()));
            Atom.add(T.LEFT, Proposition, T.RIGHT, (l, p, r) => p);

            Variable.add(T.IDENT, i => Factory.Variable(i));

            VariableList.add(VariableList, T.COMMA, Variable, (l, c, v) => { l.Add(v); return l; });
            VariableList.add(Variable, v => new List<Variable>(new[] { v }));
            VariableList.add(() => new List<Variable>());

            Grammar = new Grammar<Sentence>(Sentence);
        }

        private Proposition InfixOperator(ISentenceFactory Factory, InfixOperatorList OpList)
        {
            return PrecedenceRules.Parse(OpList.Operands, OpList.Operators);
        }

        public Grammar<Sentence> Grammar { get; private set; }

        private class InfixOperatorList
        {
            public static InfixOperatorList New(Proposition P1, string Operator, Proposition P2)
            {
                var N = new InfixOperatorList
                {
                    Operands = new List<Proposition>(),
                    Operators = new List<string>()
                };

                N.Operands.Add(P1);
                N.Operators.Add(Operator);
                N.Operands.Add(P2);

                return N;
            }

            public InfixOperatorList Add(string Operator, Proposition P)
            {
                Operators.Add(Operator);
                Operands.Add(P);
                return this;
            }

            public List<Proposition> Operands;
            public List<string> Operators;
        }

        private PrecedenceRules PrecedenceRules;
    }
}
